Visual Basic has included drag-and-drop capabilities since its early versions, but only Visual Basic 5 added a new set of properties, methods, and events that let developers implement a standard OLE-compliant mechanism and enable cross-application drag-and-drop. You can recognize these properties, methods, and events because their names begin with OLE. In this section, I'll illustrate a few possible applications of this powerful, and underutilized, feature.
Basically, a control can work as a source or as a destination of a drag-and-drop operation. Visual Basic supports two drag-and-drop modes, automatic or manual. In automatic mode, you just have to set a property at design time or at run time and let Visual Basic do everything. Conversely, in manual mode you have to respond to a number of events that occur while dragging is in progress, but in return you get better control over the process.
Most intrinsic Visual Basic controls, as well as a few external ActiveX controls, support OLE drag-and-drop in one form or another. A few controls can work only as destinations of the drag-and-drop operations; others can work both as sources and destinations. Only a few intrinsic controls can work in automatic mode. You decide how a control will actually behave as the source of a drag-and-drop operation by setting its OLEDragMode property. Similarly, you decide how a control behaves as the destination of a drag-and-drop operation by setting its OLEDropMode property. Table 9-1 summarizes the degree of OLE drag-and-drop support by intrinsic Visual Basic controls and some external ActiveX controls.
Table 9-1. You can classify controls according to their degree of support for OLE drag-and-drop features. Controls in the Windowless library support the same features as intrinsic controls.
Controls | OLEDragMode | OLEDropMode |
---|---|---|
TextBox, PictureBox, Image, RichTextBox, MaskEdBox | vbManual, vbAutomatic | vbNone, vbManual, vbAutomatic |
ComboBox, ListBox, DirListBox, FileListBox, DBCombo, DBList, TreeView, ListView, ImageCombo, DataList, DataCombo | vbManual, vbAutomatic | vbNone, vbManual |
Form, Label, Frame, CommandButton, DriveListBox, Data, MSFlexGrid, SSTab, TabStrip, Toolbar, StatusBar, ProgressBar, Slider, Animation, UpDown, MonthView, DateTimePicker, CoolBar | Not supported | vbNone, vbManual |
If a control supports automatic drag-and-drop, you simply set its OLEDragMode or its OLEDropMode property (or both) to vbAutomatic. For example, if you want your application to support drag-and-drop of RTF text, you just need to place a RichTextBox control on your form and ensure that both its OLEDragMode and OLEDropMode properties are set to 1-vbAutomatic. If you do so, you can drag portions of text to and from Microsoft Word, WordPad, and most other word processors. Of course, you can also drag-and-drop to other RichTextBox controls in your own application, as well as to TextBox and MaskEdBox controls, as you can see in Figure 9-18.
Other controls support the vbAutomatic setting for the OLEDragMode property. But in some cases the effect of such automatic drag-and-drop might not be what you expect. Just to name a few possibilities, you can drag selected items from a multiselect ListBox control into a multilined TextBox control, where the items are rendered as multiple lines of text separated by CR-LF pairs. Or you can drop a FileListBox's selected item onto another Windows application that supports that type of file. For example, use the demonstration application on the companion CD to drop a DOC or TXT file onto Microsoft Word. Note that although the DirListBox officially supports the automatic mode for OLEDragMode, no drag operation actually starts when you operate on its items.
Figure 9-18. This demo shows how you can drag-and-drop text and images to and from other Windows programs. Notice how the text is rendered differently on the RichTextBox and regular TextBox controls.
When you perform automatic drag-and-drop, your application doesn't receive any events and you have no control over the process. You can initiate the drag-and-drop operation only with the left mouse button, and by default its effect is a Move command. (That is, the data is moved to the destination and then is deleted from the source control.) If you want to perform a Copy operation, you must keep the Ctrl key pressed, as you would do inside Windows Explorer.
While automatic mode allows you to get interesting effects by simply setting some design-time properties, it's apparent that you need manual mode to tap the real power of drag-and-drop. As you'll see, this process requires you to write code in several event procedures, both for the source and target controls. Figure 9-19 summarizes the events that fire as the end user performs a drag-and-drop operation: You'll probably need to refer to this diagram while reading the rest of this section.
Figure 9-19. All the events that fire when manual drag-and-drop is enabled.
The demonstration application shown in Figure 9-20 consists of a RichTextBox control that works either as the source or the target for an OLE drag-and-drop operation. On the form you'll also find a ListBox control on which you can drop plain text, either from the RichTextBox control or another source (such as Microsoft Word). When you do this, the ListBox control will scan the text, find all unique words, sort them, and display the results to the user.
Figure 9-20. This demonstration application shows how you can use OLE drag-and-drop capabilities to create a ListBox control that automatically searches the text you drop on it, finds unique words, and sorts them.
If you want a control to be able to initiate a drag-and-drop operation, you should set its OLEDragMode property to vbManual (the default value of this property for all controls except RichTextBox), and then start the drag process by invoking its OLEDrag method. You usually do this in the MouseDown event procedure:
Private Sub rtfText_MouseDown(Button As Integer, Shift As Integer, _ x As Single, y As Single) ' Start a drag operation if right button is pressed. If Button = 2 Then rtfText.OLEDrag End Sub |
When you invoke the OLEDrag method, an OLEStartDrag event is fired for the source control. This event receives a DataObject object and an AllowedEffects parameter. You can think of the DataObject object as a recipient for the data you want to pass from the source to the target control. You store data in this object through its SetData method. As is the case with the Clipboard, you can store multiple data in different formats, as shown in Table 9-2. For example, a RichTextBox control is able to move or copy data in RTF or plain text format:
Private Sub rtfText_OLEStartDrag(Data As RichTextLib.DataObject, _ AllowedEffects As Long) ' Use selected text, or all text if no text is currently selected. If rtfText.SelLength Then Data.SetData rtfText.SelRTF, vbCFRTF Data.SetData rtfText.SelText, vbCFText Else Data.SetData rtfText.TextRTF, vbCFRTF Data.SetData rtfText.Text, vbCFText End If AllowedEffects = vbDropEffectMove Or vbDropEffectCopy End Sub |
Table 9-2. All the formats supported by the DataObject object.
Constant | Value | Meaning |
---|---|---|
vbCFText | 1 | Text |
vbCFBitmap | 2 | Bitmap (BMP) |
vbCFMetafile | 3 | Metafile (WMF) |
vbCFEMetafile | 14 | Enhanced metafile (.emf) |
vbCFDIB | 8 | Device independent bitmap (dib or bmp) |
vbCFPalette | 9 | Color palette |
vbCFFiles | 15 | List of files |
vbCFRTF | -16639 | Rich Text Format (RTF) |
You should assign the AllowedEffects parameter a value that specifies all the effects that you want to support for the drag-and-drop operation. You can assign the value 1-vbDropEffectCopy or 2-vbDropEffectMove or their sum if you want to support both effects, as in the preceding piece of code.
If a drag operation is currently active, Visual Basic raises an OLEDragOver event for all the controls the mouse hovers on. This event receives the DataObject object and the Effect value, as set by the source control, plus information on the mouse position and button state. Based on this data, you assign to the Effect parameter the one effect corresponding to the action that will be performed when the end user releases the mouse on this control. This value can be 0-vbDropEffectNone, 1-vbDropEffectCopy, 2-vbDropEffectMove, or &H80000000-vbDropEffectScroll. (The latter value means that the target control will scroll its own contents, for example, when the mouse is on the scrollbar of a ListBox control). The State parameter holds a value that specifies whether the mouse is entering or leaving the control, and can be one of the following values: 0-vbEnter, 1-vbLeave, or 2-vbOver.
Private Sub lstWords_OLEDragOver(Data As DataObject, Effect As Long, _ Button As Integer, Shift As Integer, X As Single, Y As Single, _ State As Integer) If Data.GetFormat(vbCFText) Then Effect = Effect And vbDropEffectCopy Else Effect = vbDropEffectNone End If ' As a demonstration, change the background of this ListBox when ' the mouse is over it. If State = vbLeave Then ' Restore background color on exit. lstWords.BackColor = vbWindowBackground ElseIf Effect <> 0 And State = vbEnter Then ' Change background color on entry. lstWords.BackColor = vbYellow End If End Sub |
The target control should test whether the DataObject object contains data in one of the formats the target supports. It performs this test by invoking the DataObject object's GetFormat method, as in the previous code snippet. In addition, you should always consider the Effect parameter to be a bit-field value. In the preceding case, the statement
Effect = Effect And vbDropEffectCopy |
will set its value to 0 if the source control doesn't support the Copy operation. At first you might think that this caution is excessive because you know that the RichTextBox control does support the Copy operation. But you should keep in mind that once you enable the lstWords control as a target control for a drag-and-drop operation, it can receive values from any possible source of a drag-and-drop action, inside or outside the application it belongs to; therefore, you must be prepared to deal with such cases.
Immediately after the OLEDragOver event for the source control, Visual Basic raises an OLEGiveFeedback event for the source control. In this event, the source control learns which effect was selected by the target control and possibly modifies the mouse cursor:
Private Sub lstWords_OLEGiveFeedback(Effect As Long, _ DefaultCursors As Boolean) ' If effect is Copy, use a custom cursor. If Effect = vbDropEffectCopy Then DefaultCursors = False Screen.MousePointer = vbCustom ' imgCopy is an Image control that stores a custom icon. Screen.MouseIcon = imgCopy.Picture Else DefaultCursors = True End If End Sub |
The DefaultCursors parameter should be explicitly set to False if you assign a different mouse cursor. You don't have to implement the OLEGiveFeedback event if you don't care about the cursor's shape.
When the user releases the mouse button over the target control, Visual Basic raises an OLEDragDrop event for the target control. Apart from the State parameter, this event receives the same parameters as the OLEDragOver event. In this case, the meaning of the Effect parameter is slightly different because it represents the action that was decided by the target control.
Private Sub lstWords_OLEDragDrop(Data As DataObject, Effect As Long, _ Button As Integer, Shift As Integer, X As Single, Y As Single) ' Restore the correct background color. lstWords.BackColor = vbWindowBackground ' Select Copy action if possible, otherwise select Move. If Effect And vbDropEffectCopy Then Effect = vbDropEffectCopy ElseIf Effect And vbDropEffectMove Then Effect = vbDropEffectMove End If ' In either case, ask for the data - only plain text is supported. Dim text As String text = Data.GetData(vbCFText) ' Code for processing the text and loading the list of unique ' words in the lstWords listbox (omitted)... End Sub |
Immediately after the OLEDragDrop event is executed, Visual Basic fires the source control's OLECompleteDrag event. You need to write code for this event to delete highlighted data in the source code if the action was vbDropEffectMove or to restore the control's original appearance if it changed during the drag-and-drop process:
Private Sub rtfText_OLECompleteDrag(Effect As Long) If Effect = vbDropEffectMove Then ' If this was a Move operation, delete the highlighted text. rtfText.SelText = "" Else ' If it was a Copy command, just clear the selection. rtfText.SelLength = 0 End If End Sub |
When the source control supports many formats, loading data in those formats into the DataObject object when the OLEStartDrag event fires isn't an efficient solution. Fortunately, Visual Basic supports another approach: Instead of loading the source data in the DataObject object when the drag operation begins, you just specify which formats the source control is willing to support.
' In the rtfText's OLEStartDrag event procedure Data.SetData , vbCFRTF Data.SetData , vbCFText |
If the drag-and-drop operation isn't canceled, the target control eventually invokes the DataObject's GetData method to retrieve the data in a given format. When this happens, Visual Basic fires the OLESetData event for the source control:
Private Sub rtfText_OLESetData(Data As RichTextLib.DataObject, _ DataFormat As Integer) ' This event fires only when the target control invokes the ' Data's GetData method. If DataFormat = vbCFText Then If rtfText.SelLength Then Data.SetData rtfText.SelText, vbCFText Else Data.SetData rtfText.text, vbCFText End If ElseIf DataFormat = vbCFRTF Then If rtfText.SelLength Then Data.SetData rtfText.SelRTF, vbCFRTF Else Data.SetData rtfText.TextRTF, vbCFRTF End If End If End Sub |
This event isn't invoked if you passed data to the SetData method's first argument when the drag-and-drop operation began.
As you know, Windows Explorer supports drag-and-drop of filenames, and many Windows applications can work as destinations of a file drag operation initiated inside Windows Explorer. In this section, I'll show how you can implement both these features—working as a source or a target for file drag-and-drop.
The key to these capabilities is the DataObject's Files property. If you want your application to work as a target for a file drag-and-drop operation, you check whether the DataObject object contains data in vbCFFiles format, and then you retrieve filenames by iterating on the Files collection. For example, you can load the names of the dropped files into a ListBox control using this code:
If Data.GetFormat(vbCFFiles) Then For i = 1 To Data.Files.Count lstFiles.AddItem Data.Files(i) Next End If |
Of course, you can also open the file and display its contents. The demonstration program shown in Figure 9-21 implements both possibilities.
Figure 9-21. The upper window is displaying the contents of the AutoDrop.Vbp file dropped by Windows Explorer, while the bottom window is displaying a list of filenames dropped by the file dialog on the right.
Creating an application that behaves as a source for dragging and dropping files isn't difficult either. You just have to add items to the Files collection and set the vbCFFiles format. Just remember that target applications expect the Files collection to contain filenames with their complete paths. This code shows how you can use a FileListBox control as a source for a drag operation:
Private Sub File1_OLEStartDrag(Data As DataObject, AllowedEffects As Long) Dim i As Integer, path As String path = File1.path & IIf(Right$(File1.path, 1) <> "\", "\", "") ' Add all selected files to the Data.Files collection. Data.Files.Clear For i = 0 To File1.ListCount - 1 If File1.Selected(i) Then Data.Files.Add path & File1.List(i) End If Next If Data.Files.Count Then ' Only if we actually added files Data.SetData , vbCFFiles AllowedEffects = vbDropEffectCopy End If End Sub |
The OLE drag-and-drop mechanism is even more flexible than I've demonstrated in that it also supports moving data in a proprietary format. For example, you might have a form that's displaying an invoice, an order, or information about a customer, and you want to enable the user to drag-and-drop this data on another form of your application. Using a custom format also enables you to easily transfer information among different instances of your application, and at the same time it prevents your accidentally dropping it on other programs. In this way, you can move confidential data between applications without the risk of unauthorized people peeking at it.
The first step in using a custom format is registering it with Windows, which you do by invoking the RegisterClipboardFormat API function. This step must be executed by every application that needs to access data in a custom format. Windows guarantees that the first time this function is invoked with a given custom format name (PersonalData in the code below), a unique integer will be returned and all subsequent calls to that API function with the same argument will return the same value, even if called from other applications.
Private Declare Function RegisterClipboardFormat Lib "user32" _ Alias "RegisterClipboardFormatA" (ByVal lpString As String) As Integer Dim CustomFormat As Integer Private Sub Form_Load() CustomFormat = RegisterClipboardFormat("PersonalData") End Sub |
At this point, you can store data using the CustomFormat identifier, exactly as you did with a standard format such as vbCFText or vbCFBitmap. The only difference is that custom data must be loaded into a Byte array before passing it to the DataObject.SetData method. The demonstration application in Figure 9-22 uses this technique to move or copy data about two forms:
' Code in the source application Private Sub imgDrag_OLESetData(Data As DataObject, DataFormat As Integer) Dim i As Integer, text As String, bytes() As Byte ' Build a long string made up of field contents. For i = 0 To txtField.UBound If i > 0 Then text = text & vbNullChar text = text & txtField(i) Next ' Move to a byte array, and then assign it to DataObject. bytes() = text Data.SetData bytes(), CustomFormat End Sub |
Figure 9-22. You can move data in a custom format between distinct forms or even between distinct instances of your application, without the risk of dropping your confidential data somewhere else.
The target form must retrieve the Byte array, rebuild the original string, and then extract the value of individual fields:
' Code in the target application Private Sub imgDrag_OLEDragDrop(Data As DataObject, Effect As Long, _ Button As Integer, Shift As Integer, X As Single, Y As Single) Dim bytes() As Byte, text() As String, i As Integer bytes() = Data.GetData(CustomFormat) ' Retrieve individual values, and then assign them to fields. text() = Split(CStr(bytes), vbNullChar) For i = 0 To txtField.UBound txtField(i) = text(i) Next End Sub |
Browse the source code of the demonstration application to see more information about this technique.
Now that you have mastered all the techniques concerning SDI and MDI forms, dialog boxes, and OLE drag-and-drop, you're ready to dive into the intricacies of the external ActiveX controls that come with Visual Basic. These controls are covered in the next three chapters.